package com.dospyer.jsql;

import com.dospyer.jsql.exception.DuplicateKeyException;
import com.dospyer.jsql.trx.ReadView;
import com.dospyer.jsql.trx.TransactionManager;
import com.sun.tools.javac.util.Assert;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.TreeMap;

/**
 * @author : peigen
 * Date: 2021/10/22 5:15 下午
 */
@Data
@Slf4j
public class BufferPool {

    /**
     * 单例模式
     */
    private static volatile BufferPool bufferPool;

    private BufferPool() {
    }

    public static BufferPool getInstance() {
        if (bufferPool == null) {
            synchronized (BufferPool.class) {
                if (bufferPool == null) {
                    bufferPool = new BufferPool();
                }
            }
        }
        return bufferPool;
    }

    /**
     * 简化版实现
     */
    private TreeMap<Long, Row> pool = new TreeMap<>();

    /**
     * 简化版 insert 实现
     *
     * @param row
     */
    public synchronized void insert(Row row) {
        Assert.checkNonNull(row, "记录不能为空！");
        Assert.checkNonNull(row.getId(), "记录 ID 不能为空！");
        if (pool.containsKey(row.getId())) {
            throw new DuplicateKeyException("记录 ID " + row.getId() + " 已存在！");
        }
        row.setTrxId(TransactionManager.getInstance().getTrxId());
        pool.put(row.getId(), row);
    }

    /**
     * 简化版 update 实现
     *
     * @param row
     */
    public boolean updateById(Row row) {
        Assert.checkNonNull(row, "记录不能为空！");
        Assert.checkNonNull(row.getId(), "记录 ID 不能为空！");
        Row old = pool.remove(row.getId());
        if (old == null) {
            log.warn("记录不存在 {}", row);
            return false;
        }

        row.setTrxId(TransactionManager.getInstance().getTrxId());
        row.setRollPointer(old);
        pool.put(row.getId(), row);
        return true;
    }

    /**
     * 获取当前事务可见的版本
     *
     * @param id
     */
    public Row getByIdWithMvcc(Long id) {
        Assert.checkNonNull(id, "记录 ID 不能为空！");
        if (!pool.containsKey(id)) {
            log.warn("记录 ID {} 不存在！", id);
            return null;
        }
        Row record = pool.get(id);
        ReadView readView = TransactionManager.getInstance().getReadView();
        // 如果不可见，查询历史版本
        while (record != null && !readView.isVisible(record.getTrxId())) {
            record = record.getRollPointer();
        }
        return record;
    }
//
//    /**
//     * free 链表
//     */
//    private ConcurrentLinkedQueue free = new ConcurrentLinkedQueue();
//    /**
//     * flush 链表，修改了缓存页数据，还没同步到磁盘
//     */
//    private ConcurrentLinkedQueue flush = new ConcurrentLinkedQueue();
//
//    /**
//     * 优化1：
//     * 区分冷热区域的 LRU 链表。
//     * 可以通过查看系统变量 innodb_old_blocks_pct 的值来确定 old 区域在 LRU 链表中所占的比例
//     * old 区域在 LRU 链表中所占的比例是 37%，也就是说 old 区域大约占 LRU 链表的 3/8
//     * SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
//     *
//     * 首次被加载到 BufferPool 的页，会被放到 old 区的头部。
//     *
//     * 后续再次访问该页面时如果时间超过了指定的时间之后才将页面移动到young区的头部。（防止全表扫描等场景）
//     * 间隔时间是由系统变量 innodb_old_blocks_time 控制，默认值为1000，单位是毫秒。
//     * SHOW VARIABLES LIKE 'innodb_old_blocks_time';
//     *
//     * 优化2：
//     * 缓存页已经在 young 区的前1/4，不会进行移动，避免频繁的移动。
//     * 缓存页位于young区域的1/4的后边，才会被移动到LRU链表头部，这样就可以降低调整LRU链表的频率，从而提升性能。
//     * 也就是说如果某个缓存页对应的节点在young区域的1/4中，再次访问该缓存页时也不会将其移动到LRU链表头部。
//     *
//     */
//
//    /**
//     * lru链表 young 区，保存热数据
//     */
//    private ConcurrentLinkedQueue youngLru = new ConcurrentLinkedQueue();
//
//    /**
//     * lru链表 old 区，保存冷数据
//     */
//    private ConcurrentLinkedQueue oldLru = new ConcurrentLinkedQueue();

}
